// 版权所有2011 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 包gcimporter为gc生成的对象文件实现导入。
package gcimporter // 导入“go/internal/gcimporter”

import (
	"bufio"
	"fmt"
	"go/build"
	"go/token"
	"go/types"
	"io"
	"os"
	"path/filepath"
	"strings"
)

// 调试/开发支持
const debug = false

var pkgExts = [...]string{".a", ".o"}

// FindPkg根据build提供的包信息返回导入
// 路径的文件名和唯一包id。导入（使用
// build.Default build.Context）。相对srcDir被解释为
// 相对于当前工作目录。
// 如果找不到文件，则返回一个空文件名。
// 
func FindPkg(path, srcDir string) (filename, id string) {
	if path == "" {
		return
	}

	var noext string
	switch {
	default:
		// “x”->“$GOPATH/pkg/$GOOS_U$GOARCH/x.ext”，“x”
		// /不要求源文件存在。
		if abs, err := filepath.Abs(srcDir); err == nil { // 请参阅问题14282 
			srcDir = abs
		}
		bp, _ := build.Import(path, srcDir, build.FindOnly|build.AllowBinary)
		if bp.PkgObj == "" {
			id = path // 确保我们在错误消息
			return
		}
		noext = strings.TrimSuffix(bp.PkgObj, ".a")
		id = bp.ImportPath

	case build.IsLocalImport(path):
		// “/x”->”/this/directory/x.ext“，“/this/directory/x”
		noext = filepath.Join(srcDir, path)
		id = noext

	case filepath.IsAbs(path):
		// 中有要打印的id，仅供完整性参考-go/build。导入
		// 不支持绝对导入
		// “/x”->“/x.ext”、“/x”
		noext = path
		id = path
	}

	if false { // 用于调试
		if path != id {
			fmt.Printf("%s -> %s\n", path, id)
		}
	}

	// 尝试扩展
	for _, ext := range pkgExts {
		filename = noext + ext
		if f, err := os.Stat(filename); err == nil && !f.IsDir() {
			return
		}
	}

	filename = "" // 未找到
	return
}

// 导入gc生成的包，给定其导入路径和srcDir，将
// 相应的包对象添加到包映射，并返回对象。
// 包映射必须包含所有已导入的包。
// 
func Import(fset *token.FileSet, packages map[string]*types.Package, path, srcDir string, lookup func(path string) (io.ReadCloser, error)) (pkg *types.Package, err error) {
	var rc io.ReadCloser
	var id string
	if lookup != nil {
		// 如果指定了自定义查找，则假定调用方已将
		// 路径转换为规范导入路径，以便在映射中使用。
		if path == "unsafe" {
			return types.Unsafe, nil
		}
		id = path

		// 如果之前完全导入了包，则无需重新导入。
		if pkg = packages[id]; pkg != nil && pkg.Complete() {
			return
		}
		f, err := lookup(path)
		if err != nil {
			return nil, err
		}
		rc = f
	} else {
		var filename string
		filename, id = FindPkg(path, srcDir)
		if filename == "" {
			if path == "unsafe" {
				return types.Unsafe, nil
			}
			return nil, fmt.Errorf("can't find import: %q", id)
		}

		// 如果在
		if pkg = packages[id]; pkg != nil && pkg.Complete() {
			return
		}

		// 打开文件
		f, err := os.Open(filename)
		if err != nil {
			return nil, err
		}
		defer func() {
			if err != nil {
				// 将文件名添加到错误
				err = fmt.Errorf("%s: %v", filename, err)
			}
		}()
		rc = f
	}
	defer rc.Close()

	var hdr string
	buf := bufio.NewReader(rc)
	if hdr, err = FindExportData(buf); err != nil {
		return
	}

	switch hdr {
	case "$$\n":
		err = fmt.Errorf("import %q: old textual export format no longer supported (recompile library)", path)

	case "$$B\n":
		var exportFormat byte
		exportFormat, err = buf.ReadByte()

		// 索引的导出格式以“i”开头，则无需重新导入；旧的
		// 二进制导出格式以“c”、“d”或“v”开头
		// （从“版本”开始）。选择合适的进口商。
		if err == nil && exportFormat == 'i' {
			pkg, err = iImportData(fset, packages, buf, id)
		} else {
			err = fmt.Errorf("import %q: old binary export format no longer supported (recompile library)", path)
		}

	default:
		err = fmt.Errorf("import %q: unknown export data header: %q", path, hdr)
	}

	return
}

type byPath []*types.Package

func (a byPath) Len() int           { return len(a) }
func (a byPath) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a byPath) Less(i, j int) bool { return a[i].Path() < a[j].Path() }
